import Koa from 'koa';
import * as fs from 'node:fs';
import * as path from 'node:path';
import os from 'node:os';
import mime from 'mime-types';
import * as XLSX from 'xlsx';
import { promisify } from 'node:util';
import BaseConfig from '@/Config/Base';
import jwt from 'jsonwebtoken';
import JsonConfig from '/Frame/Plugins/JsonConfig';
import nodemailer from 'nodemailer';

export class globalFunc {

    public JsonConfig = JsonConfig;
    private nodemailerTransporter:any; // 邮件发送器

    /**
     * 生成新的Token值
     * @param payload 负载
     * @param expiresIn 过期时间 s 秒 60s m 分钟 5m h 小时 1h d 天 7d w 周 2w y 年 1y
     * @param secret 密钥默认为配置文件中的密钥
     * @returns 
     */
    newToken(payload: object, expiresIn: string = '1h', secret: string = BaseConfig.secretKey): string {
        return (jwt as any).sign(payload, secret, { algorithm: 'HS256', expiresIn });
    }


    /**
     * 验证Token值是否有效
     * @param token token值
     * @param secret 密钥默认为配置文件中的密钥
     * @returns 
     */
    tokenVerify(token: string, secret: string = BaseConfig.secretKey): string | jwt.JwtPayload | null {
        try {
            const decoded = jwt.verify(token, secret, { algorithms: ['HS256'], complete: false });
            return decoded;
        } catch (err) {
            // console.log((err as Error).message);
            return null;
        }
    }


    /**
     * 解码Token值(不管是否过期) 错误返回null
     * @param token token值
     * @param secret 密钥默认为配置文件中的密钥
     * @returns 
     */
    tokenDecode(token: string, secret: string = BaseConfig.secretKey): string | jwt.JwtPayload | null {
        try {
            return jwt.verify(token, secret, { algorithms: ['HS256'], complete: false });
        } catch (err) {
            if ((err as Error).message !== 'jwt expired') return null;//不是过期错误就返回null
            try {
                return jwt.decode(token, { complete: false });
            } catch (err) {
                return null;//解码失败就返回null
            }
        }
    }


    /**
     * 获取当前年月日
     * @param {Object} 
     * @returns {String}
     */
    getNowFormatDate(dateObj: null | Date = null): string {
        const date = dateObj || new Date();
        const seperator1 = "-";
        const year = date.getFullYear();
        let month: string | number = date.getMonth() + 1;
        let strDate: string | number = date.getDate();
        if (month >= 1 && month <= 9) month = "0" + month;
        if (strDate >= 0 && strDate <= 9) strDate = "0" + strDate;
        let currentdate = year + seperator1 + month + seperator1 + strDate;
        return this.dateAddDays(currentdate);
    }

    /**
     * 获取当前（指定时间对象）时间的年月日时分秒
     * @param dateObj 日期对象
     * @returns 
     */
    getCurrentDateTime(dateObj: null | Date = null): string {
        const now = dateObj || new Date();
        const year = now.getFullYear();
        const month = String(now.getMonth() + 1).padStart(2, '0');
        const day = String(now.getDate()).padStart(2, '0');
        const hours = String(now.getHours()).padStart(2, '0');
        const minutes = String(now.getMinutes()).padStart(2, '0');
        const seconds = String(now.getSeconds()).padStart(2, '0');
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    }


    /**
     * [dateAddDays 从某个日期增加n天后的日期]
     * @param  {string} dateStr  [日期字符串] 如：2024-11-20
     * @param  {number} dayCount [增加的天数]
     * @return {string}[增加n天后的日期字符串]
    */
    dateAddDays(dateStr: string, dayCount: number = 0) {
        const tempDate = new Date(dateStr.replace(/-/g, "/"));//把日期字符串转换成日期格式
        const resultDate = new Date((tempDate.getTime() / 1000 + (86400 * dayCount)) * 1000);//增加n天后的日期
        const resultDateStr = resultDate.getFullYear() + "-" + (resultDate.getMonth() + 1) + "-" + (resultDate.getDate());//将日期转化为字符串格式
        return resultDateStr;
    }

    /**
     * 获取当前局域网IP
     * @returns 
     */
    getCurrentLanIP(): string {
        const interfaces = os.networkInterfaces();
        for (const devName in interfaces) {
            const iface = interfaces[devName]?.filter((details: os.NetworkInterfaceInfo) => {
                return details.family === 'IPv4' && !details.internal;
            });

            if (iface && iface.length > 0) {
                return iface[0].address;
            }
        }
        return 'IP not found';
    }



    /**
     * 利用redis自定义session 管理
     */
    get redisSession() {
        const prefix = 'FuncBase:redisSession:';
        const tl = 60 * 60 * 24;
        return {
            /**
             * 获取指定 mysession 的值
             * @param key 
             * @returns 
             */
            async get(key: string | number) {
                const result = await redis.get(prefix + key);
                if (!result) return null;
                const remainingTTL = await redis.ttl(prefix + key);
                if (remainingTTL < 60 * 60 * 12) await redis.expire(prefix + key, tl);
                return result;
            },
            /**
             * 设置指定 mysession 的值
             * @param key 
             * @param value 
             * @returns 
             */
            set(key: string | number, value: string | number) {
                return redis.set(prefix + key, value, {
                    EX: tl
                });
            }
        };
    }

    /**
     * 发送邮件
     * @param {string} to 接收人 多个逗号分隔
     * @param {string} html 主题内容
     * @param {string} subject 主标题
     * @param {string} sender 发送者名称
     * @param {any} newTransporter 新的传输方式配置
     * @returns {Promise<{ status: boolean; msg: string }>}
     */
    sendMail(to: string, html: string, subject: string, sender: string = BaseConfig.mailer.sender, newTransporter: any = undefined): Promise<{ status: boolean; msg: string }> {
        const mailer = BaseConfig.mailer;
        if(!this.nodemailerTransporter || newTransporter){
            // 创建一个传输方式
            this.nodemailerTransporter = nodemailer.createTransport({
                pool: true, // 使用连接池
                host: mailer.host, // 这是你使用第三方应用发送的主机名，QQ邮箱默认是 "smtp.qq.com"
                port: mailer.port || 465, // 端口号
                secure: mailer.secure || true,
                auth: {
                    user: mailer.email, // 发送方的邮箱
                    pass: mailer.pass // 你的授权码或者密码
                },
                tls: {
                    rejectUnauthorized: false,
                    minVersion: "TLSv1.2"
                },
                ...newTransporter ?? {}
            });
        }


        return new Promise((resolve, reject) => {
            let mailOptions: nodemailer.SendMailOptions = {
                from: `"${sender}" <${mailer.email}>`, // 发送者名称 和 邮箱地址
                to, // 发给谁，填写邮箱
                subject, // 主标题
                html // html 邮件信息主体，这里面应该把你想发的信息包含在这里，比如验证码！
            };

            let isMore: string[] | null = to.match(/[^,]+/g);
            if (isMore && isMore.length > 1) {
                mailOptions.to = isMore[0]; // 取第一个
                mailOptions.bcc = isMore.slice(1).join(',');
            }

            // 发送邮件，调用 sendMail 方法发送邮件
            this.nodemailerTransporter.sendMail(mailOptions, (error:any, info:any) => { // 第一个参数是你写的邮箱内容，一个对象，第二个参数是回调函数
                if (error) { // 回调函数的第一个参数是错误信息，第二个参数没用过，具体可以查看官网
                    logger.warn(`Email: "${sender}" <${mailer.email}> to ${to} Error:${error}`);
                    resolve({ status: false, msg: '发送失败:' + error });
                } else {
                    // 发送成功后你的操作
                    logger.info(`Email: "${sender}" <${mailer.email}> to ${to} send successfully!`);
                    resolve({ status: true, msg: '发送成功' });
                }
            });
        });
    }

}

export default (app: Koa<Koa.DefaultState, Koa.DefaultContext>) => {

    /**当前是否强路由 */
    app.context.isRouteStrong = false;

    /**
     * 获取当前年月日
     * @param {Object} 
     * @returns {String}
     */
    app.context.getNowFormatDate = globalFunc.prototype.getNowFormatDate;

    /**
     * 获取当前（指定时间对象）时间的年月日时分秒
     * @param dateObj 日期对象
     * @returns 
     */
    app.context.getCurrentDateTime = globalFunc.prototype.getCurrentDateTime;

    /**
     * [dateAddDays 从某个日期增加n天后的日期]
     * @param  {string} dateStr  [日期字符串] 如：2024-11-20
     * @param  {number} dayCount [增加的天数]
     * @return {string}[增加n天后的日期字符串]
    */
    app.context.dateAddDays = globalFunc.prototype.dateAddDays;

    app.context.upload = function (key = 'file', randName = true, suffix = [], size = (1024 * 1024 * 200), paths = '') {
        return new Promise((resolve, reject) => {
            let fileObj = this.request.files[key];
            if (!fileObj) {
                return resolve({ status: false, msg: 'key not found' });
            }

            let fileNameInfo = path.parse(fileObj.newFilename);
            if (suffix.length > 0) {
                let ext = path.extname(String(fileObj.newFilename)).toLowerCase().replace(/^\.(.+)$/, '$1');
                let extArr = suffix.filter(v => String(v).toLowerCase() == ext);
                if (extArr.length <= 0) {
                    return resolve({ status: false, msg: 'The format does not match' });
                }
            }

            if (fileObj.size > Number(size)) {
                return resolve({ status: false, msg: 'File exceeds maximum ' + Number(size) + ' limit' });
            }

            if (!randName) {
                fileNameInfo.name = path.parse(fileObj.originalFilename).name;//使用旧名字
            }
            let storageMdr = 'Public/upload/' + this.getNowFormatDate() + '/';
            if (paths) {
                storageMdr = paths;//使用用户提供的目录
                if (!/.*\/$/.test(storageMdr)) {
                    storageMdr += "/";//结尾必须斜杠
                }
            }

            const reader = fs.createReadStream(fileObj.filepath);
            // 目录不存在创建
            if (!fs.existsSync(storageMdr)) fs.mkdirSync(storageMdr, { recursive: true });
            let storagePath = storageMdr + fileNameInfo.name + String(fileNameInfo.ext).toLowerCase();

            const upStream = fs.createWriteStream(storagePath);
            reader.pipe(upStream);
            // 监听 'finish' 事件，表示数据传输完成
            upStream.on('finish', () => {
                resolve({ status: true, msg: 'successfully upload', path: storagePath.replace(/^static(.+)$/, '$1') });
            });
            upStream.on('error', (err) => {
                resolve({ status: false, msg: err.message });
            });
        });
    }

    /**
     * 获取客户端ip
     * @returns 
     */
    app.context.getClientIp = function (): string | null {
        let api = this.request.ip;
        if (String(api).indexOf('::ffff:') !== -1) {
            api = api.substring(7);
        } else if (!api) {
            api = null;
        }
        return api;
    }

    /**
     * 获取指定路径的文件大小和响应头
     * @param {string} pathstr 
     * @returns {object}
     */
    app.context.filehead = function (pathstr: string) {
        const fileInfo = fs.statSync(pathstr);
        const mimeType = mime.lookup(pathstr); // 获取文件类型
        return {
            'Content-Length': fileInfo.size,
            'Content-Type': mimeType,
        }
    }


    /**
     * 输出媒体(图片/视频)
     * @param {string} pathstr 绝对路径
     * @returns 
     */
    app.context.media = function (pathstr: string) {
        this.set(this.filehead(pathstr));
        return promisify(fs.readFile)(pathstr);
    }

    /**
     * 输出文件（下载）
     * @param {string} pathstr 绝对路径
     * @param {string?} fileName 文件名
     * @param {string?} suffix 后缀 如 .jpg
     * @returns 
     */
    app.context.download = function (pathstr: string, fileName: string | null = null, suffix: string | null = null) {
        const fileStream = fs.createReadStream(pathstr);
        const fileSize = fs.statSync(pathstr).size;
        const filePathInfo = path.parse(pathstr);
        if (!fileName) fileName = filePathInfo.name;
        if (suffix && !/^\..+$/.test(suffix)) {
            suffix = '.' + suffix;
        } else if (!suffix) suffix = filePathInfo.ext;
        fileName += suffix;
        // 设置响应头
        this.set({
            'Content-disposition': `attachment; filename=${encodeURIComponent((fileName as string))}`,
            'Content-type': 'application/octet-stream',
            // 'Transfer-Encoding':'chunked',
            'Content-length': fileSize.toString(),
        });
        return fileStream;
    }


    /**
     * 生成并输出下载一个工作表
     * @param SheetName 工作表名
     * @param Data 数据
     * @returns 
     */
    app.context.outXlsx = function (SheetName: string, ...Data: any[][]) {
        // 创建一个新的工作簿
        const workbook = XLSX.utils.book_new();
        Data.forEach((D, I) => {
            // 将数据添加进工作表
            const worksheet = XLSX.utils.aoa_to_sheet(D);
            // 将工作表添加到工作簿
            XLSX.utils.book_append_sheet(workbook, worksheet, SheetName + I);
        });
        // 将工作簿写入缓冲区
        const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
        // 设置响应头
        this.set({
            'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'Content-Disposition': `attachment; filename="${encodeURIComponent(SheetName)}.xlsx"`
        });
        return buffer;
    }


    /**
     * 输出json成功响应
     * @param data 对象或者数据
     * @param msg 消息 默认 ok
     * @param code 状态码 默认 200
     * @returns 
     */
    app.context.json = function (data: { [k: string]: any } | any, msg: string = 'ok', code: number = 200) {
        return {
            code,
            msg,
            data
        }
    }


    /**
     * 生成新的Token值
     * @param payload 负载
     * @param expiresIn 过期时间 s 秒 60s m 分钟 5m h 小时 1h d 天 7d w 周 2w y 年 1y
     * @param secret 密钥默认为配置文件中的密钥
     * @returns 
     */
    app.context.newToken = globalFunc.prototype.newToken;

    /**
     * 验证Token值是否有效
     * @param token token值
     * @param secret 密钥默认为配置文件中的密钥
     * @returns 
     */
    app.context.tokenVerify = globalFunc.prototype.tokenVerify;

    /**
     * 解码Token值(不管是否过期) 错误返回null
     * @param token token值
     * @param secret 密钥默认为配置文件中的密钥
     * @returns 
     */
    app.context.tokenDecode = globalFunc.prototype.tokenDecode;
























    return async (ctx: Koa.Context, next: Koa.Next) => {
        await next();
    }
}